package glog

import (
	"fmt"
	"log"
	"os"
	"runtime"
	"sync"
	"time"
)

const (
	FATAL = 0
	ERROR = 1
	WARN  = 2
	INFO  = 3
	DEBUG = 4
)

var strLog = [...]string{
	"\033[0;33m[CRIT]\033[0m ",
	"\033[0;31m[EROR]\033[0m ",
	"\033[0;35m[WARN]\033[0m ",
	"\033[0;32m[INFO]\033[0m ",
	"\033[0;36m[DEBG]\033[0m ",
}

type Glogger struct {
	mu                 sync.Mutex
	buf                []byte
	file               *os.File
	basename, pathname string
	level              int
	day                int
}

func NewGlogger(name string, level int) *Glogger {
	pathname := ""
	basename := name
	for i := len(name) - 1; i > 0; i-- {
		if name[i] == '/' {
			basename = name[i+1:]
			pathname = name[:i+1]
			break
		}
	}

	year, month, day := time.Now().Date()
	filename := fmt.Sprintf("%s_%04d%02d%02d.log", basename, year, month, day)

	file, err := os.OpenFile(pathname+filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Panic(err)
	}

	logger := new(Glogger)
	logger.pathname = pathname
	logger.basename = basename
	logger.level = level
	logger.file = file
	logger.day = day
	return logger
}

func (l *Glogger) Output(depth int, level int, s string) error {
	now := time.Now()
	_, fname, line, ok := runtime.Caller(depth)
	if !ok {
		fname = "???"
		line = 0
	}

	l.mu.Lock()
	l.buf = l.buf[:0]
	day := format(&l.buf, level, now, fname, line)
	if day != l.day {
		l.file.Close()
		year, month, day := now.Date()
		filename := fmt.Sprintf("%s_%04d%02d%02d.log", l.basename, year, month, day)
		file, err := os.OpenFile(l.pathname+filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			return err
		}
		l.day = day
		l.file = file
	}
	l.buf = append(l.buf, s...)
	if len(s) == 0 || s[len(s)-1] != '\n' {
		l.buf = append(l.buf, '\n')
	}
	_, err := l.file.Write(l.buf)
	l.mu.Unlock()
	return err
}

// Cheap integer to fixed-width decimal ASCII.  Give a negative width to avoid zero-padding.
func itoa(buf *[]byte, i int, wid int) {
	// Assemble decimal in reverse order.
	var b [20]byte
	bp := len(b) - 1
	for i >= 10 || wid > 1 {
		wid--
		q := i / 10
		b[bp] = byte('0' + i - q*10)
		bp--
		i = q
	}
	// i < 10
	b[bp] = byte('0' + i)
	*buf = append(*buf, b[bp:]...)
}

func format(buf *[]byte, level int, t time.Time, filename string, line int) int {
	// date format
	year, month, day := t.Date()
	itoa(buf, year, 4)
	*buf = append(*buf, '/')
	itoa(buf, int(month), 2)
	*buf = append(*buf, '/')
	itoa(buf, day, 2)
	*buf = append(*buf, ' ')

	hour, min, sec := t.Clock()
	itoa(buf, hour, 2)
	*buf = append(*buf, ':')
	itoa(buf, min, 2)
	*buf = append(*buf, ':')
	itoa(buf, sec, 2)
	//*buf = append(*buf, '.')
	//itoa(buf, t.Nanosecond()/1e3, 6)
	*buf = append(*buf, ' ')

	// loglevel format
	*buf = append(*buf, strLog[level]...)

	// filename format
	for i := len(filename) - 1; i > 0; i-- {
		if filename[i] == '/' {
			filename = filename[i+1:]
			break
		}
	}
	*buf = append(*buf, filename...)
	*buf = append(*buf, ':')
	itoa(buf, line, -1)
	*buf = append(*buf, ": "...)

	return day
}

func (l *Glogger) Debug(format string, v ...interface{}) {
	if l.level >= DEBUG {
		l.Output(2, DEBUG, fmt.Sprintln(fmt.Sprintf(format, v...)))
	}
}

func (l *Glogger) Info(format string, v ...interface{}) {
	if l.level >= INFO {
		l.Output(2, INFO, fmt.Sprintln(fmt.Sprintf(format, v...)))
	}
}

func (l *Glogger) Warn(format string, v ...interface{}) {
	if l.level >= WARN {
		l.Output(2, WARN, fmt.Sprintln(fmt.Sprintf(format, v...)))
	}
}

func (l *Glogger) Error(format string, v ...interface{}) {
	if l.level >= ERROR {
		l.Output(2, ERROR, fmt.Sprintln(fmt.Sprintf(format, v...)))
	}
}

func (l *Glogger) Fatal(format string, v ...interface{}) {
	l.Output(2, FATAL, fmt.Sprintln(fmt.Sprintf(format, v...)))
	os.Exit(1)
}
